#ifdef Lighting

#ifdef NumberOfLightsWithProfile

void ComputeProfileLighting(inout LightingResult res, inout LightingData iData, in BRDFParameters iParameters)
{
#ifdef PDSFX_ENABLE_CUSTOM_BRDF_LAYER
	iData.lightType = LIGHT_TYPE_PROFILE;
#endif
	for (int i = 0; i < vMin(NumberOfLightsWithProfile, _V_MAX_LIGHTS); ++i)
	{
#ifdef LightChannels
		if ((cb_cbVolMaterial.u_LightChannels & cb_LightChannels.ProfileChannels[i]) == 0)
			continue;
#endif
		iData.L = _sbCbLightsWithProfile.WorldMatrix[i][3].xyz - iData.P;
		float lightDistSquare = vDot(iData.L, iData.L);
		iData.L *= 1. / vSqrt(lightDistSquare);
		float lightAttenuation = 1.0 / lightDistSquare;
		iData.irradiance = lightAttenuation * _sbCbLightsWithProfile.PowerFactor[i] * _sbCbLightsWithProfile.Color[i];

		// Compute IES attenuation
		vec4 iesSampleDirection = _sbCbLightsWithProfile.WorldMatrix[i] * vec4(iData.L, 0.0);
		float phiCoord = (iesSampleDirection.x * 0.5) + 0.5;
		float theta = vAtan(iesSampleDirection.y, iesSampleDirection.z);
		float thetaCoord = theta * INV_PI;
#ifdef LightProfileMapAsArray
		vec3 texCoord = vec3(thetaCoord, phiCoord, float(_sbCbLightsWithProfile.MapIndex[i]));
#else
		vec2 texCoord = vec2(thetaCoord, phiCoord);
#endif
		float iesProfileScale = vTexture(lightProfileMap, texCoord).r;

		iData.irradiance *= iesProfileScale;
		iData.visibility = vMax(1.0, iData.minVisibility);
		iData.lightID = i;

		ComputeRenderingEquation(iData, iParameters, res);
	}
}

#endif

#ifdef NumberOfAreaLights

#define InvalidAreaType -1
#define SphereAreaType 0
#define TubeAreaType 1
#define DiskAreaType 2
#define RectangleAreaType 3

struct AreaLightData
{
	vec3 closestPoint;
	float lightDist;
	float lightRadius;
	float lightSpecAtt; // Manage some hacks
};

vec3 computeAreaSpecularBRDF(in LightingData iData, in AreaLightData iAreaData, in BRDFParameters iParameters)
{
#ifndef EVisuPBR
	vec3 l = vNormalize(iAreaData.closestPoint);
	vec3 H = vHalfVector(iData.V, l);
	//
	float dot_N_V = vMax(dot(iData.N, iData.V), 0.0);
	float dot_N_L = vMax(dot(iData.N, l), 0.0);
	float dot_N_H = vMin(dot(iData.N, H), 1.0 - kEpsilon); // This clamp is required to avoid some distribution computation issues with low power lights.
#if defined(AnisotropicDistribution) && defined(Fabrics)
	float roughness = computeAnisotropicRoughness(iData.T, iData.B, iData.N, H, iParameters.roughness, iParameters.anisotropyAngle, iParameters.anisotropy);
#else
	float roughness = computeRoughness(iParameters.roughness);
#endif
	float alphaPrime = vClamp(iAreaData.lightRadius / (iAreaData.lightDist * 2.0) + vPow2(roughness), 0.0, 1.0);

	float D = 1.;
	{
#if defined(AnisotropicDistribution) && !defined(Fabrics)
		D = AnisotropicGGXNormalDistribution(iData.T, iData.B, iData.N, H, dot_N_H, r, iParameters.anisotropyAngle, iParameters.anisotropy);
#else
		D = NormalDistribution(dot_N_H, roughness);
#endif
		D = vMax(D*alphaPrime, 0.0);
	}

	float LoH = max(dot(l, H), kEpsilon);
	vec3 F = OptimizedSchlickFresnel(iParameters.f0, iParameters.f90, LoH);

	float Vis = UnrealVisibility(roughness, dot_N_L, dot_N_V);
	return dot_N_L * Vis * D * F * iAreaData.lightSpecAtt;
#else
	vec3 n = iData.N;
	vec3 v = iData.V;
	vec3 l = vNormalize(iAreaData.closestPoint);
	vec3 h = normalize(l + v);
	vec3 t = normalize(iData.T);
	vec3 b = normalize(iData.B);

	float NoL = max(dot(n, l), 0.0);
	float NoV = max(dot(n, v), 0.0);
	float NoH = max(dot(n, h), 0.0);
	float LoH = max(dot(l, h), 0.0);

	fresnel_dielectric_parameters fresnelParams;
	fresnel_dielectric_init(fresnelParams, iParameters.albedo, iParameters.ior, iParameters.specularTint, iParameters.specular, iParameters.metallic);

	vec3 F = fresnel_dielectric(fresnelParams, LoH);

	float alphaPrime = vClamp(iAreaData.lightRadius / (iAreaData.lightDist * 2.0) + vPow2(iParameters.roughness), 0.0, 1.0);

	distribution_ggx_parameters ggxParams;
	distribution_ggx_initialize_area(ggxParams, iParameters.roughness, iParameters.anisotropy, alphaPrime);

	return NoL * distribution_ggx_area(ggxParams, fresnelParams, n, v, l, h, t, b, NoL, NoV, NoH, LoH) * iAreaData.lightSpecAtt;
#endif
}

//ToDo code cleanup
#define NEW_TUBE_LIGHT 3
#define NEW_ALL_LIGHT 5

void ComputeAreaLightEquation(in LightingData iData, in AreaLightData iAreaData, in BRDFParameters iParameters, inout LightingResult ioResult)
{
#if defined(EVisuPBR) && defined(NewAreaLights) && (NewAreaLights == NEW_TUBE_LIGHT || NewAreaLights == NEW_ALL_LIGHT)
	vec3 n = iData.N;
	vec3 v = iData.V;
	vec3 l = iData.L;
	vec3 h = normalize(l + v);
	vec3 t = normalize(iData.T);
	vec3 b = normalize(iData.B);

	float NoL = max(dot(n, l), 0.0);
	float NoV = max(dot(n, v), 0.0);
	float NoH = max(dot(n, h), 0.0);
	float LoH = max(dot(l, h), 0.0);

	vec3 baseSpec = computeAreaSpecularBRDF(iData, iAreaData, iParameters);
	vec3 f_s = vec3(0.0);
	vec3 flakes = vec3(0.0);
	vec3 coating = vec3(0.0);
	float base_weight = 1.0;

#ifdef SO_EVISUPBR_FLAKES
	{
		float total_flake_roughness = iParameters.flakeRoughness*iParameters.flakeRoughness;

		float flake_orientation_roughness;
		float flake_cos_cone_angle;

		float flake_coverage = iParameters.flakeCoverage;
		float flake_cone_roughness;

		distribute_flake_roughness(total_flake_roughness, flake_orientation_roughness, flake_cos_cone_angle, flake_cone_roughness);

		float a = 0.001 * (iParameters.flakeSize * 0.5);
		float avg_cell_area = 1.5 * sqrt(3.0) * a * a;
		float cells_per_area = 1.0 / avg_cell_area;

		mat3 mat = transpose(mat3(_vPrivGetWorldMatrix()));
		float scale = max(max(length(mat[0]), length(mat[1])), length(mat[2]));

		vec3 position_object = EVisuPBR_vLocalPosition;
		vec3 position_object_meters = 0.001 * scale * position_object;

		vec3 p_ddx = dFdxFine(position_object_meters);
		vec3 p_ddy = dFdyFine(position_object_meters);

		vec2 footprint_uv;
		vec2 footprint_uv_ddx;
		vec2 footprint_uv_ddy;
		triplanar_mapping(position_object_meters, p_ddx, p_ddy, EVisuPBR_vLocalNormal, footprint_uv, footprint_uv_ddx, footprint_uv_ddy);

		float footprint_det = footprint_uv_ddx.x * footprint_uv_ddy.y - footprint_uv_ddx.y * footprint_uv_ddy.x;
		float footprint_area = abs(footprint_det);

		float flakes_per_area = flake_coverage * cells_per_area;
		float expected_num_flakes_inside_footprint = footprint_area * flakes_per_area;

		float log_cells_in_footprint = log(footprint_area / avg_cell_area);

		float closeup_weight = 0.0;
		float stochastic_weight = 0.0;
		float smooth_weight = 0.0;
		vec3 voronoi_cell_orientation = vec3(0.0);

#ifdef SO_EVISUPBR_FLAKE_ACCURACY_GOAL
#if SO_EVISUPBR_FLAKE_ACCURACY_GOAL == 0
		//Quality
		float closeup_stochastic_transition = 1.0;
		float closeup_smooth_transition = 64.0;
		float stochastic_smooth_transition = 131072.0;
#elif SO_EVISUPBR_FLAKE_ACCURACY_GOAL == 1
		//Performance
		float closeup_stochastic_transition = 1.0;
		float closeup_smooth_transition = 4.0;
		float stochastic_smooth_transition = 1024.0;
#endif
#endif

		model_weights(closeup_stochastic_transition, closeup_smooth_transition, stochastic_smooth_transition, flake_coverage, log_cells_in_footprint, base_weight, closeup_weight, stochastic_weight, smooth_weight, voronoi_cell_orientation, flake_orientation_roughness, cells_per_area, footprint_uv, footprint_uv_ddx, footprint_uv_ddy);

		float cos_cone_angle = flake_cos_cone_angle;
		vec3 front_flake_orientation = voronoi_cell_orientation;
		float cone_solid_angle = 2.0 * PI * (1.0 - cos_cone_angle);
		vec3 front_flake_orientation_view = normalize(front_flake_orientation.x*t - front_flake_orientation.y*b + front_flake_orientation.z*n);
		vec3 r = normalize(reflect(-v, front_flake_orientation_view));
		bool inside_flake_cone = dot(r, l) > cos_cone_angle;

		if (closeup_weight > 1e-6 && inside_flake_cone && front_flake_orientation.z > 0.0)
		{
			float single_flake_eval = 1.0 / (cone_solid_angle * abs(dot(l, n)));
			flakes += iParameters.flakeColor * closeup_weight * single_flake_eval;
		}

		if (stochastic_weight > 1e-6)
		{
			float flakes_contributing = 0.0;
			float flakes_in_footprint = 0.0;
			stochastic_flakes(flake_orientation_roughness, cos_cone_angle, expected_num_flakes_inside_footprint, cells_per_area, footprint_uv, footprint_uv_ddx, footprint_uv_ddy, flake_coverage, -v, l, n, flakes_contributing, flakes_in_footprint);
			float dotVH = dot(v, h);
			float flake_count_threshold = 10.0;
			float alpha = min(1.0, flakes_in_footprint / flake_count_threshold);
			float reference_flake_count = mix(expected_num_flakes_inside_footprint, flakes_in_footprint, alpha);
			reference_flake_count = max(0.01, reference_flake_count);

			float d = flakes_contributing / (cone_solid_angle * reference_flake_count);
			distribution_ggx_parameters params;
			distribution_ggx_initialize(params, flake_orientation_roughness, 0.0);
			float g = distribution_ggx_g(params, n, v, l, t, b, NoL, NoV);
			flakes += stochastic_weight * iParameters.flakeColor * dotVH * g * d / abs(dot(v, n) * dot(l, n));
		}

		if (smooth_weight > 1e-6)
		{
			distribution_ggx_parameters paramsSmooth;
			distribution_ggx_initialize(paramsSmooth, iParameters.flakeRoughness, 0.0);
			float smooth_flake = distribution_ggx(paramsSmooth, n, v, l, h, t, b, NoL, NoV, NoH, LoH);
			flakes += smooth_weight * iParameters.flakeColor * smooth_flake;
		}
	}

#endif

	fresnel_dielectric_parameters fresnelParams;
	fresnel_dielectric_init(fresnelParams, iParameters.albedo, iParameters.ior, iParameters.specularTint, iParameters.specular, iParameters.metallic);

	vec3 F1 = fresnel_dielectric(fresnelParams, NoV);
	vec3 F2 = fresnel_dielectric(fresnelParams, NoL);

	distribution_ggx_parameters ggxParams;
	distribution_ggx_initialize(ggxParams, iParameters.roughness, iParameters.anisotropy);

	vec3 f_r = distribution_ggx(ggxParams, fresnelParams, n, v, l, h, t, b, NoL, NoV, NoH, LoH);

	vec3 f_b = vec3(0.0);
	float b_weight = 1.0;
#ifdef SO_EVISUPBR_SHEEN
	distribution_inverted_ashikhmin_parameters sheenParams;
	distribution_inverted_ashikhmin_initialize(sheenParams, iParameters.sheen);
	b_weight = vPow(1.0 - iParameters.sheen, 5.0);
	vec3 rho_b = iParameters.albedo * (1.0 - iParameters.metallic)*(1.0 - iParameters.transparency) * (1.0 - b_weight);
	f_b = max(vec3(0.0), rho_b * distribution_inverted_ashikhmin(sheenParams, NoH) / (4.0 * (NoL + NoV - NoL*NoV)) * (vec3(1.0) - vMax(F1, F2)));
#endif

	distribution_modified_lambert_parameters diffParam;
	distribution_modified_lambert_init(diffParam, iParameters.albedo, iParameters.metallic, iParameters.transparency);

	vec3 f_d = vec3(0.0);
#if defined(LightMap) && (LightMap == LightMapIlluminanceMode)
#ifdef FragmentStage
	vec3 lightMapAtt = vSaturate(vTexture(lightMapTexture, _vPrivGetLightMapUVs()).xyz);
	f_r *= lightMapAtt;
	f_b *= lightMapAtt;
#endif
#else
	f_d = distribution_modified_lambert(diffParam, NoV, NoL, F1, F2) * b_weight;
#endif

#ifdef SO_EVISUPBR_SUBSURFACE
	f_d *= getScattering(dot(n, l), iParameters.curvature);
#else
	f_d *= NoL;
#endif

	f_r *= NoL;
	f_b *= NoL;
	f_s *= NoL;

	base_weight = clamp(base_weight, 0.0, 1.0);

	vec3 sum = mix(baseSpec, f_d + baseSpec + f_b, base_weight);

#ifdef SO_EVISUPBR_CLEARCOAT
	{
		distribution_ggx_parameters params;
		distribution_ggx_initialize(params, iParameters.clearcoatRoughness, 0.0);
		vec3 n = iParameters.clearcoatNormal;
		float NoL = max(dot(n, l), 0.0);
		float NoV = max(dot(n, v), 0.0);
		float NoH = max(dot(n, h), 0.0);

		const float clearcoatF0 = 0.04;

		float F = clearcoatF0 + (1.0 - clearcoatF0) * pow(1.0 - LoH, 5.0);
		float F1 = clearcoatF0 + (1.0 - clearcoatF0) * pow(1.0 - NoL, 5.0);
		float F2 = clearcoatF0 + (1.0 - clearcoatF0) * pow(1.0 - NoV, 5.0);

		float f_coat = distribution_ggx(params, n, v, l, h, t, b, NoL, NoV, NoH, LoH);
		f_coat *= iParameters.clearcoat * F;

		coating = sum * (1.0 - iParameters.clearcoat * max(F1, F2)) + f_coat;
	}
#endif

#ifdef SO_EVISUPBR_SUBSURFACE
	{
		float F_0 = pow((1.0 - iParameters.ior) / (1.0 + iParameters.ior), 2.0);
		vec3 rho_sr0 = (1.0 - iParameters.metallic) * F_0 * iParameters.specular * iParameters.specularTint + iParameters.metallic * iParameters.albedo;
		float rho_sr90 = (1.0 - iParameters.metallic) * iParameters.specular + iParameters.metallic;
		ioResult.translucency += iData.irradiance * translucencyBRDF(iData.N, iData.L, iData.lightID, iData.V, iParameters.albedo, rho_sr0, vec3(rho_sr90));
	}
#endif
	ioResult.radiance += iData.irradiance  *((baseSpec + f_d + f_b + flakes + coating)* iData.visibility);
#else
	vec3 H = vHalfVector(iData.V, iData.L);

	vec3 baseSpec = computeAreaSpecularBRDF(iData, iAreaData, iParameters);
#if !defined(LightMap) || (LightMap != LightMapIlluminanceMode)
	vec3 baseDiff = computeDiffuseBRDF(iData, iParameters, H);
#else
	vec3 baseDiff = vec3(0.0);
#ifdef FragmentStage
	baseSpec *= vSaturate(vTexture(lightMapTexture, _vPrivGetLightMapUVs()).xyz);
#endif
#endif

#ifdef PDSFX_ENABLE_BRDF_CONTROL
	// TODO: implement for EVisuPBR !!!
	baseDiff *= iParameters.diffuseBRDFFactor;
	baseSpec *= iParameters.specularBRDFFactor;
#endif

#ifdef Flakes
	vec3 flakes = computeMetallicFlakesBRDF(iData, iParameters, H) + computePearlFlakesBRDF(iData, iParameters, H);
#else
	vec3 flakes = vec3(0.0);
#endif

#ifdef Coating
	vec3 coating = computeCoatingBRDF(iData, iParameters, H);
#else
	vec3 coating = vec3(0.0);
#endif

#ifdef PDSFX_ENABLE_CUSTOM_BRDF_LAYER
	BRDFLayerParameters params;
	// TODO: implement for EVisuPBR !!!
	params.fresnel = OptimizedSchlickFresnel(iParameters.f0, iParameters.f90, vDot(iData.L, H));
	params.irradiance = iData.irradiance;
	params.lightType = iData.lightType;
	params.lightID = iData.lightID;
	ioResult.radiance += ComputeCustomBRDFLayer(params);
#endif
	ioResult.radiance += iData.irradiance  *(baseSpec + baseDiff + flakes + coating)* iData.visibility;
#endif
}

vec3 closestPointOnLine(vec3 a, vec3 b, vec3 c)
{
	vec3 ab = b - a;
	float t = vDot(c - a, ab) / vDot(ab, ab);
	return a + t * ab;
}

vec3 closestPointOnSegment(vec3 a, vec3 b, vec3 c)
{
	vec3 ab = b - a;
	float t = vDot(c - a, ab) / vDot(ab, ab);
	return a + vClamp(t, 0.0, 1.0) * ab;
}

float rectangleSolidAngle(vec3 worldPos, vec3 p0, vec3 p1, vec3 p2, vec3 p3)
{
	vec3 v0 = p0 - worldPos;
	vec3 v1 = p1 - worldPos;
	vec3 v2 = p2 - worldPos;
	vec3 v3 = p3 - worldPos;

	vec3 n0 = vNormalize(vCross(v0, v1));
	vec3 n1 = vNormalize(vCross(v1, v2));
	vec3 n2 = vNormalize(vCross(v2, v3));
	vec3 n3 = vNormalize(vCross(v3, v0));

	float g0 = vAcos(vDot(-n0, n1));
	float g1 = vAcos(vDot(-n1, n2));
	float g2 = vAcos(vDot(-n2, n3));
	float g3 = vAcos(vDot(-n3, n0));

	return g0 + g1 + g2 + g3 - 2.0 * PI;
}

#ifdef NewAreaLights

#define LUT_SIZE 64.0
#define LUT_SCALE (LUT_SIZE - 1.0)/LUT_SIZE
#define LUT_BIAS 0.5/LUT_SIZE
#define NEW_SPHERE_LIGHT 1
#define NEW_DISK_LIGHT 2
#define NEW_RECT_LIGHT 4


#if defined(Coating) || defined(SO_EVISUPBR_CLEARCOAT)
#define COATINGF0 vec3(0.04)
#define COATINGF90 vec3(1.0)
#endif // Coating

float vMaxComp(in vec2 v) {
	return vMax(v.x, v.y);
}

float vMaxComp(in vec3 v) {
	return vMax(vMaxComp(v.xy), v.z);
}

float vMaxComp(in vec4 v) {
	return vMax(vMaxComp(v.xy), vMaxComp(v.zw));
}

#ifdef EVisuPBR

struct evisuBRDFData
{
	vec3 albedoEvisu;
	float F0;
	vec3 sr0Color;
	vec3 sr90Color;
	vec3 specularEnergyConservationConstant;
	float diffuseEnergyConservationConstant;
#ifdef SO_EVISUPBR_ver21x
	float sheenEnergyConservationConstant;
#endif
};

evisuBRDFData evisuMaterialData;

#ifdef SO_EVISUPBR_FLAKES

struct evisuPBRFlakesData
{
	float smooth_weight;
	vec3 stochasticHemisphereFlakesOrientation;
	float stochastic_weight;
	float closeup_weight;
	vec3 voronoi_cell_orientation;
	float base_weight;
};

evisuPBRFlakesData evisuFlakesData;

#endif
#endif

struct areaLightingData
{
	mat3 Minv;
	vec2 fresnel;
#if defined(Coating) || defined(SO_EVISUPBR_CLEARCOAT)
	mat3 MinvCC;
	vec2 fresnelCC;
#endif
#ifdef SO_EVISUPBR_SHEEN
	mat3 MinvSheen;
#endif
#ifdef Flakes
	mat3 MinvMetal;
	vec2 fresnelMetal;
	mat3 MinvMetalFlakes;
	vec2 fresnelMetalFlakes;
	// Pearl flakes		
#if INTERNAL_FLAKES_PERFORMANCE_MODE < 1
	mat3 MinvPearlFlakes;
	vec2 fresnelPearlFlakes;
#endif
#endif

#if SO_EVISUPBR_FLAKES
	mat3 MinvMetal;
	vec2 fresnelMetal;
	mat3 MinvMetalSto;
	vec2 fresnelMetalSto;
	mat3 MinvMetalClose;
	vec2 fresnelMetalClose;
#endif
};

areaLightingData areaGGXData;

#ifdef SO_EVISUPBR_SHEEN
void setAreaSheenData(in float roughness, in float NoV, out mat3 Minv) {
	vec2 uvAr = vec2(roughness, 1.0 - vSqrt(1.0 - NoV)) * LUT_SCALE + LUT_BIAS;
#ifdef SO_EVISUPBR_ver21x
	vec4 t1 = vTexture(AreaLightGGX, vec3(uvAr, 3));
#else
	vec4 t1 = vTexture(AreaLightGGX, vec3(uvAr, 2));
#endif
	Minv = mat3(
		vec3(t1.x, 0, t1.y),
		vec3(0, 1.0, 0),
		vec3(t1.z, 0, t1.w)
	);
}
#endif

void setAreaGGXData(in float roughness, in float NoV, out vec2 fresnel, out mat3 Minv) {
	vec2 uvAr = vec2(roughness, 1.0 - vSqrt(1.0 - NoV)) * LUT_SCALE + LUT_BIAS;
	vec4 t1 = vTexture(AreaLightGGX, vec3(uvAr, 0));
	vec4 t2 = vTexture(AreaLightGGX, vec3(uvAr, 1));
	Minv = mat3(
		vec3(t1.x, 0, t1.y),
		vec3(0, 1.0, 0),
		vec3(t1.z, 0, t1.w)
	);
	fresnel = vec2(t2.z, t2.y);
}

void computeAreaGGXData(in BRDFParameters materialData, in LightingData iData) {
	float NoV = vSaturate(vDot(iData.N, iData.V));

	setAreaGGXData(materialData.roughness, NoV, areaGGXData.fresnel, areaGGXData.Minv);

#ifdef Coating
	setAreaGGXData(materialData.coatingRoughness, NoV, areaGGXData.fresnelCC, areaGGXData.MinvCC);
#endif

#ifdef SO_EVISUPBR_CLEARCOAT
	setAreaGGXData(materialData.clearcoatRoughness, vSaturate(vDot(materialData.clearcoatNormal, iData.V)), areaGGXData.fresnelCC, areaGGXData.MinvCC);
#endif

#ifdef SO_EVISUPBR_SHEEN
#ifdef SO_EVISUPBR_ver21x
	setAreaSheenData(materialData.sheenRoughness, NoV, areaGGXData.MinvSheen);
#else
	setAreaSheenData(materialData.sheen, NoV, areaGGXData.MinvSheen);
#endif
#endif

#ifdef Flakes
	setAreaGGXData(materialData.metalRoughness, vSaturate(vDot(iData.N, iData.V)), areaGGXData.fresnelMetal, areaGGXData.MinvMetal);
	setAreaGGXData(materialData.metallicFlakesRoughness, vSaturate(vDot(iData.metalFlakesN, iData.V)), areaGGXData.fresnelMetalFlakes, areaGGXData.MinvMetalFlakes);
	// Pearl flakes		
#if INTERNAL_FLAKES_PERFORMANCE_MODE < 1
	setAreaGGXData(materialData.pearlFlakesRoughness, vSaturate(vDot(iData.pearlFlakesN, iData.V)), areaGGXData.fresnelPearlFlakes, areaGGXData.MinvPearlFlakes);
#endif
#endif
#ifdef SO_EVISUPBR_FLAKES
	if (evisuFlakesData.smooth_weight > 0.0) {
		setAreaGGXData(materialData.flakeRoughness, NoV, areaGGXData.fresnelMetal, areaGGXData.MinvMetal);
	}
	if (evisuFlakesData.stochastic_weight > 0.0) {
		vec3 normalStoFlakes = vNormalize(iData.V + evisuFlakesData.stochasticHemisphereFlakesOrientation);
		float NdotVSto = vSaturate(vDot(normalStoFlakes, iData.V));
		setAreaGGXData(materialData.flakeRoughness, NdotVSto, areaGGXData.fresnelMetalSto, areaGGXData.MinvMetalSto);
	}
	if (evisuFlakesData.closeup_weight > 0.0) {
		vec3 normalCloseUpFlakes = vNormalize(iData.V + evisuFlakesData.voronoi_cell_orientation);
		float NdotVClose = vSaturate(vDot(normalCloseUpFlakes, iData.V));
		setAreaGGXData(materialData.flakeRoughness, NdotVClose, areaGGXData.fresnelMetalClose, areaGGXData.MinvMetalClose);
	}
#endif 
}

vec3 getGeomT(in vec3 N) {
	vec3 normN = vNormalize(N);
	float x = normN.x;
	float y = normN.y;
	float z = normN.z;

	vec3 T = vec3(0.0);

	if (vAbs(z) > 0.0) {
		T.y += 0.0;
		T.x += 1.0;
		T.z += -x / z;
		return vNormalize(T);
	}
	if (vAbs(y) > 0.0) {
		T.x += 0.0;
		T.z += 1.0;
		T.y += -z / y;
		return vNormalize(T);
	}

	if (vAbs(x) > 0.0) {
		T.z += 0.0;
		T.y += 1.0;
		T.x += -y / x;
		return vNormalize(T);
	}
	return vec3(0.0);
}

void getSphereLightPoints(inout vec3 lP, in vec3 P, in float radius, out vec3 p0, out vec3 p1, out vec3 p2) {
	vec3 toUse = vNormalize(P - lP);
	lP += radius * toUse;
	vec3 right = getGeomT(toUse.xyz);
	vec3 up = getGeomBApprox(toUse.xyz, right);  //getGeomB replaced with in-built getGeomBApprox
	p0 = lP.xyz + right * radius - up * radius;
	p1 = lP.xyz - right * radius - up * radius;
	p2 = lP.xyz - right * radius + up * radius;
}

// An extended version of the implementation from
// "How to solve a cubic equation, revisited"
// http://momentsingraphics.de/?p=105
vec3 SolveCubic(vec4 Coefficient) {
	// Normalize the polynomial
	Coefficient.xyz /= Coefficient.w;
	// Divide middle coefficients by three
	Coefficient.yz /= 3.0;

	float A = Coefficient.w;
	float B = Coefficient.z;
	float C = Coefficient.y;
	float D = Coefficient.x;

	// Compute the Hessian and the discriminant
	vec3 Delta = vec3(
		-Coefficient.z*Coefficient.z + Coefficient.y,
		-Coefficient.y*Coefficient.z + Coefficient.x,
		vDot(vec2(Coefficient.z, -Coefficient.y), Coefficient.xy)
	);

	float Discriminant = vDot(vec2(4.0*Delta.x, -Delta.y), Delta.zy);

	vec3 RootsA, RootsD;

	vec2 xlc, xsc;

	// Algorithm A
	{
		float A_a = 1.0;
		float C_a = Delta.x;
		float D_a = -2.0*B*Delta.x + Delta.y;

		// Take the cubic root of a normalized complex number
		float Theta = vAtan(vSqrt(Discriminant), -D_a) / 3.0;

		float x_1a = 2.0*vSqrt(-C_a)*vCos(Theta);
		float x_3a = 2.0*vSqrt(-C_a)*vCos(Theta + (2.0 / 3.0)*3.14159265);

		float xl;
		if ((x_1a + x_3a) > 2.0*B)
			xl = x_1a;
		else
			xl = x_3a;

		xlc = vec2(xl - B, A);
	}

	// Algorithm D
	{
		float A_d = D;
		float C_d = Delta.z;
		float D_d = -D*Delta.y + 2.0*C*Delta.z;

		// Take the cubic root of a normalized complex number
		float Theta = vAtan(D*vSqrt(Discriminant), -D_d) / 3.0;

		float x_1d = 2.0*vSqrt(-C_d)*vCos(Theta);
		float x_3d = 2.0*vSqrt(-C_d)*vCos(Theta + (2.0 / 3.0)*3.14159265);

		float xs;
		if (x_1d + x_3d < 2.0*C)
			xs = x_1d;
		else
			xs = x_3d;

		xsc = vec2(-D, xs + C);
	}

	float E = xlc.y*xsc.y;
	float F = -xlc.x*xsc.y - xlc.y*xsc.x;
	float G = xlc.x*xsc.x;

	vec2 xmc = vec2(C*F - B*G, -B*F + C*E);

	vec3 Root = vec3(xsc.x / xsc.y, xmc.x / xmc.y, xlc.x / xlc.y);

	if (Root.x < Root.y && Root.x < Root.z)
		Root.xyz = Root.yxz;
	else if (Root.z < Root.x && Root.z < Root.y)
		Root.xyz = Root.xzy;

	return Root;
}

float areaDiskLight(vec3 N, vec3 V, vec3 P, mat3 Minv, vec3 p0, vec3 p1, vec3 p2) {
	// build TBN
	vec3 T = vNormalize(V - N*vDot(V, N));
	vec3 B = vCross(N, T);

	// rotate area light in (T1, T2, N) basis
	mat3 R = vTranspose(mat3(T, B, N));

	vec3 L[3];
	L[0] = R * (p0 - P);
	L[1] = R * (p1 - P);
	L[2] = R * (p2 - P);

	vec3 c = Minv *(0.5 * (L[0] + L[2]));
	vec3 v1 = Minv *(0.5 * (L[1] - L[2]));
	vec3 v2 = Minv *(0.5 * (L[1] - L[0]));

	if (vDot(vCross(v1, v2), c) < 0.0)
		return 0.0;

	float a, b;
	float d11 = vDot(v1, v1);
	float d22 = vDot(v2, v2);
	float d12 = vDot(v1, v2);
	if (vAbs(d12) / vSqrt(d11*d22) > 0.005) {
		float tr = d11 + d22;
		float det = -d12*d12 + d11*d22;

		det = vSqrt(det);
		float u = 0.5*vSqrt(tr - 2.0*det);
		float v = 0.5*vSqrt(tr + 2.0*det);
		float e_max = vPow((u + v), 2);
		float e_min = vPow((u - v), 2);

		vec3 v1_, v2_;

		if (d11 > d22) {
			v1_ = d12*v1 + (e_max - d11)*v2;
			v2_ = d12*v1 + (e_min - d11)*v2;
		}
		else {
			v1_ = d12*v2 + (e_max - d22)*v1;
			v2_ = d12*v2 + (e_min - d22)*v1;
		}

		a = 1.0 / e_max;
		b = 1.0 / e_min;
		v1 = vNormalize(v1_);
		v2 = vNormalize(v2_);
	}
	else {
		a = 1.0 / vDot(v1, v1);
		b = 1.0 / vDot(v2, v2);
		v1 *= vSqrt(a);
		v2 *= vSqrt(b);
	}

	vec3 v3 = vNormalize(vCross(v1, v2));
	if (vDot(c, v3) < 0.0)
		v3 *= -1.0;

	float L_ = vDot(v3, c);
	float x0 = vDot(v1, c) / L_;
	float y0 = vDot(v2, c) / L_;

	float E1 = vInversesqrt(a);
	float E2 = vInversesqrt(b);

	a *= L_*L_;
	b *= L_*L_;

	float c0 = a*b;
	float c1 = a*b*(1.0 + x0*x0 + y0*y0) - a - b;
	float c2 = 1.0 - a*(1.0 + x0*x0) - b*(1.0 + y0*y0);
	float c3 = 1.0;

	vec3 roots = SolveCubic(vec4(c0, c1, c2, c3));
	float e1 = roots.x;
	float e2 = roots.y;
	float e3 = roots.z;

	vec3 avgDir = vec3(a*x0 / (a - e2), b*y0 / (b - e2), 1.0);

	mat3 rotate = mat3(v1, v2, v3);

	avgDir = rotate*avgDir;
	avgDir = vNormalize(avgDir);

	float L1 = vSqrt(-e2 / e3);
	float L2 = vSqrt(-e2 / e1);

	float formFactor = L1*L2*vInversesqrt((1.0 + L1*L1)*(1.0 + L2*L2));

	// use tabulated horizon-clipped sphere
	vec2 uv = vec2(avgDir.z*0.5 + 0.5, formFactor);
	uv = uv*LUT_SCALE + LUT_BIAS;
	float scale = vTexture(AreaLightGGX, vec3(uv, 1)).w;

	float res = formFactor*scale;

	return vSaturate(res);
}

//SpecGloss++
float SpecGlossEnergyConservationTerm(in vec3 fresnel) {
	vec3 luminanceVector = vec3(0.299, 0.587, 0.114);
	return 1.0 - vDot(fresnel, luminanceVector);
}
//SpecGloss--

//DSPBR ++
#ifdef EVisuPBR

float vLerp(in float a, in float b, in float w) {
	return a + w * (b - a);
}

float AverageDirectionalMultipleAlbedo(in float roughness) {
	float a2 = vPow4(roughness);
	return 1.0 + a2 * (-0.133 + a2 * (-1.8695 + a2 * (2.2268 - 0.83397*a2)));
}

vec3 AverageMultipleFresnel(in vec3 sr0Color, in vec3 sr90Color) {
	return vMix(sr90Color, sr0Color, 0.95238095238);
}

float DirectionalMultipleAlbedo(in float cosTheta, in float roughness) {
	float a2 = vPow4(roughness);
	float res = 3.09507 + cosTheta * (-9.11369 + cosTheta * (15.8884 + cosTheta * (-13.70343 + 4.51786 * cosTheta)));
	res *= (-0.20277 + a2 * (2.772 + a2 * (-2.6175 + 0.73343*a2)));
	res *= 1.4594 * a2 * cosTheta;
	return 1.0 - res;
}

float DirectionalAlbedo(in float F0, in vec3 specularTint, in float cosTheta, in float roughness) {
	float a2 = vPow4(roughness);
	float E0 = F0 * vMaxComp(specularTint);
	return vLerp(E0 + (1.0 - E0) * vPow5(1.0 - cosTheta), 0.04762 + 0.95238*E0, 1.0 - vPow5(1.0 - a2));
}

float AverageDirectionalAlbedo(in float F0, in vec3 specularTint, in float roughness) {
	float a2 = vPow4(roughness);
	float E0 = F0 * vMaxComp(specularTint);
	return E0 + (-0.33263 * a2 - 0.072359) * (1.0 - E0) * E0;
}

#ifdef SO_EVISUPBR_ver21x
float DirectionalSheenAlbedo(in float cosTheta, in float roughness) {
	return 0.04495972 + 0.47479907 * (sqrt(1.0 - cosTheta) + (1.0 - cosTheta) * sqrt(1.0 - sqrt(roughness)));
}
#endif

void computeBRDFParamforEvisu(in BRDFParameters materialData) {
	evisuMaterialData.albedoEvisu = materialData.albedo * (1 - materialData.metallic) * (1.0 - materialData.transparency);
	evisuMaterialData.F0 = vPow2((materialData.ior - 1.0) / (materialData.ior + 1.0));
	evisuMaterialData.sr0Color = (1.0 - materialData.metallic) * evisuMaterialData.F0 * materialData.specular * materialData.specularTint + materialData.metallic * materialData.albedo;
	evisuMaterialData.sr90Color = vec3(1.0) * ((1.0 - materialData.metallic) * materialData.specular + materialData.metallic);
}

void computeEvisuEnergyConservation(in vec3 N, in vec3 V, in BRDFParameters materialData) {

	float NoV = vSaturate(vDot(N, V));

	float averageDirectionalMultipleAlbedo = AverageDirectionalMultipleAlbedo(materialData.roughness);
	vec3 averageMultipleFresnel = AverageMultipleFresnel(evisuMaterialData.sr0Color, evisuMaterialData.sr90Color);

	evisuMaterialData.specularEnergyConservationConstant = vPow2(1.0 - DirectionalMultipleAlbedo(NoV, materialData.roughness)) / vMax(1.0 - averageDirectionalMultipleAlbedo, 1e-6) * (averageMultipleFresnel * averageMultipleFresnel * averageDirectionalMultipleAlbedo) / vMax(1.0 - averageMultipleFresnel * (1.0 - averageDirectionalMultipleAlbedo), vec3(1e-6));
	evisuMaterialData.diffuseEnergyConservationConstant = vMix(1.0, vPow2(1.0 - DirectionalAlbedo(evisuMaterialData.F0, materialData.specularTint, NoV, materialData.roughness)) / vMax(1.0 - AverageDirectionalAlbedo(evisuMaterialData.F0, materialData.specularTint, materialData.roughness), 1e-6), materialData.specular);
#ifdef	SO_EVISUPBR_SHEEN
#ifdef SO_EVISUPBR_ver21x
	evisuMaterialData.sheenEnergyConservationConstant = materialData.sheen * vMaxComp(materialData.sheenColor) * DirectionalSheenAlbedo(NoV, materialData.sheenRoughness);
#endif
#endif
}

#ifdef SO_EVISUPBR_FLAKES
void computeFlakesParam(in BRDFParameters iParameters, in LightingData iData)
{
	float total_flake_roughness = iParameters.flakeRoughness*iParameters.flakeRoughness;

	float flake_orientation_roughness;
	float flake_cos_cone_angle;

	float flake_coverage = iParameters.flakeCoverage;
	float flake_cone_roughness;

	distribute_flake_roughness(total_flake_roughness, flake_orientation_roughness, flake_cos_cone_angle, flake_cone_roughness);

	float a = 0.001 * (iParameters.flakeSize * 0.5);
	float avg_cell_area = 1.5 * sqrt(3.0) * a * a;
	float cells_per_area = 1.0 / avg_cell_area;

	mat3 mat = transpose(mat3(_vPrivGetWorldMatrix()));
	float scale = max(max(length(mat[0]), length(mat[1])), length(mat[2]));

	vec3 position_object = EVisuPBR_vLocalPosition;
	vec3 position_object_meters = 0.001 * scale * position_object;

	vec3 p_ddx = dFdxFine(position_object_meters);
	vec3 p_ddy = dFdyFine(position_object_meters);


	vec2 footprint_uv;
	vec2 footprint_uv_ddx;
	vec2 footprint_uv_ddy;
	triplanar_mapping(position_object_meters, p_ddx, p_ddy, EVisuPBR_vLocalNormal, footprint_uv, footprint_uv_ddx, footprint_uv_ddy);

	float footprint_det = footprint_uv_ddx.x * footprint_uv_ddy.y - footprint_uv_ddx.y * footprint_uv_ddy.x;
	float footprint_area = abs(footprint_det);

	float flakes_per_area = flake_coverage * cells_per_area;
	float expected_num_flakes_inside_footprint = footprint_area * flakes_per_area;

	float log_cells_in_footprint = log(footprint_area / avg_cell_area);

#ifdef SO_EVISUPBR_FLAKE_ACCURACY_GOAL
#if SO_EVISUPBR_FLAKE_ACCURACY_GOAL == 0
	//Quality
	float closeup_stochastic_transition = 1.0;
	float closeup_smooth_transition = 64.0;
	float stochastic_smooth_transition = 131072.0;
#elif SO_EVISUPBR_FLAKE_ACCURACY_GOAL == 1
	//Performance
	float closeup_stochastic_transition = 1.0;
	float closeup_smooth_transition = 4.0;
	float stochastic_smooth_transition = 1024.0;
#endif
#endif

	model_weights(closeup_stochastic_transition, closeup_smooth_transition, stochastic_smooth_transition, flake_coverage, log_cells_in_footprint, evisuFlakesData.base_weight, evisuFlakesData.closeup_weight, evisuFlakesData.stochastic_weight, evisuFlakesData.smooth_weight, evisuFlakesData.voronoi_cell_orientation, flake_orientation_roughness, cells_per_area, footprint_uv, footprint_uv_ddx, footprint_uv_ddy);
	if (evisuFlakesData.stochastic_weight > 0.0)
	{
		evisuFlakesData.stochasticHemisphereFlakesOrientation = vec3(0.0);
		contributing_flakes_in_footprint_stochastic_hemisphere(flake_orientation_roughness, flake_cos_cone_angle, flake_coverage, cells_per_area, footprint_uv, footprint_uv_ddx, footprint_uv_ddy, -iData.V, iData.L, iData.N, evisuFlakesData.stochasticHemisphereFlakesOrientation);
	}
}
#endif
#endif

//DSPBR --

//Rect Light ++

float IntegrateEdge(in vec3 v1, in vec3 v2)
{
	float x = vDot(v1, v2);
	float y = vAbs(x);

	float a = 0.8543985 + (0.4965155 + 0.0145206*y)*y;
	float b = 3.4175940 + (4.1616724 + y)*y;
	float v = a / b;

	float theta_sintheta = (x > 0.0) ? v : 0.5*vInversesqrt(1.0 - x*x) - v;

	return (vCross(v1, v2)*theta_sintheta).z;
}

void ClipQuad(inout vec3 L[5], out int n)
{
	// detect clipping config
	int config = 0;
	if (L[0].z > 0.0) config += 1;
	if (L[1].z > 0.0) config += 2;
	if (L[2].z > 0.0) config += 4;
	if (L[3].z > 0.0) config += 8;

	// clip
	n = 0;

	if (config == 0)
	{
		// clip all
	}
	else if (config == 1)
	{
		n = 3;
		L[1] = -L[1].z * L[0] + L[0].z * L[1];
		L[2] = -L[3].z * L[0] + L[0].z * L[3];
	}
	else if (config == 2)
	{
		n = 3;
		L[0] = -L[0].z * L[1] + L[1].z * L[0];
		L[2] = -L[2].z * L[1] + L[1].z * L[2];
	}
	else if (config == 3)
	{
		n = 4;
		L[2] = -L[2].z * L[1] + L[1].z * L[2];
		L[3] = -L[3].z * L[0] + L[0].z * L[3];
	}
	else if (config == 4)
	{
		n = 3;
		L[0] = -L[3].z * L[2] + L[2].z * L[3];
		L[1] = -L[1].z * L[2] + L[2].z * L[1];
	}
	else if (config == 5)
	{
		n = 0;
	}
	else if (config == 6)
	{
		n = 4;
		L[0] = -L[0].z * L[1] + L[1].z * L[0];
		L[3] = -L[3].z * L[2] + L[2].z * L[3];
	}
	else if (config == 7)
	{
		n = 5;
		L[4] = -L[3].z * L[0] + L[0].z * L[3];
		L[3] = -L[3].z * L[2] + L[2].z * L[3];
	}
	else if (config == 8)
	{
		n = 3;
		L[0] = -L[0].z * L[3] + L[3].z * L[0];
		L[1] = -L[2].z * L[3] + L[3].z * L[2];
		L[2] = L[3];
	}
	else if (config == 9)
	{
		n = 4;
		L[1] = -L[1].z * L[0] + L[0].z * L[1];
		L[2] = -L[2].z * L[3] + L[3].z * L[2];
	}
	else if (config == 10)
	{
		n = 0;
	}
	else if (config == 11)
	{
		n = 5;
		L[4] = L[3];
		L[3] = -L[2].z * L[3] + L[3].z * L[2];
		L[2] = -L[2].z * L[1] + L[1].z * L[2];
	}
	else if (config == 12)
	{
		n = 4;
		L[1] = -L[1].z * L[2] + L[2].z * L[1];
		L[0] = -L[0].z * L[3] + L[3].z * L[0];
	}
	else if (config == 13)
	{
		n = 5;
		L[4] = L[3];
		L[3] = L[2];
		L[2] = -L[1].z * L[2] + L[2].z * L[1];
		L[1] = -L[1].z * L[0] + L[0].z * L[1];
	}
	else if (config == 14)
	{
		n = 5;
		L[4] = -L[0].z * L[3] + L[3].z * L[0];
		L[0] = -L[0].z * L[1] + L[1].z * L[0];
	}
	else if (config == 15)
	{
		n = 4;
	}

	if (n == 3)
		L[3] = L[0];
	if (n == 4)
		L[4] = L[0];
}

float areaRectangleLight(in vec3 N, in vec3 V, in vec3 P, in mat3 Minv, in vec3 p0, in vec3 p1, in vec3 p2, in vec3 p3) {
	// build TBN
	vec3 T = vNormalize(V - N*vDot(V, N));
	vec3 B = vCross(N, T);

	mat3 M = Minv*vTranspose(mat3(T, B, N));

	float res = 0.0;

	vec3 L[5];
	L[0] = M * (p0 - P);
	L[1] = M * (p1 - P);
	L[2] = M * (p2 - P);
	L[3] = M * (p3 - P);

	int config = 0;
	ClipQuad(L, config);
	if (config == 0) {
		return 0.0;
	}
	L[0] = vNormalize(L[0]);
	L[1] = vNormalize(L[1]);
	L[2] = vNormalize(L[2]);
	L[3] = vNormalize(L[3]);
	L[4] = vNormalize(L[4]);

	res += IntegrateEdge(L[0], L[1]);
	res += IntegrateEdge(L[1], L[2]);
	res += IntegrateEdge(L[2], L[3]);
	if (config >= 4)
		res += IntegrateEdge(L[3], L[4]);
	if (config == 5)
		res += IntegrateEdge(L[4], L[0]);

	return vSaturate(res);
}

void areaRectangleLightModel(out vec3 specular, out vec3 diffuse, out vec3 translucency, in vec3 N, in vec3 V, in vec3 P, in vec3 p0, in vec3 p1, in vec3 p2, in vec3 p3, in BRDFParameters materialData, in LightingData iData) {
#ifdef AnisotropicDistribution
	vec3 bitangent = vNormalize(vSin(2.0 * PI * materialData.anisotropyAngle) * iData.T + vCos(2.0 * PI * materialData.anisotropyAngle) * iData.B);
	vec3 anisotropicTangent = vCross(bitangent, -V);
	vec3 anisotropicNormal = vCross(anisotropicTangent, bitangent);
	vec3 bentNormal = vNormalize(vMix(N, anisotropicNormal, 0.22*materialData.anisotropy));
	vec3 coreView = vReflect(vReflect(V, bentNormal), N);
#else
	vec3 coreView = V;
#endif
#ifdef EVisuPBR
	specular = evisuMaterialData.sr0Color * areaGGXData.fresnel.x + evisuMaterialData.sr90Color * areaGGXData.fresnel.y;
#else
	specular = materialData.f0 * areaGGXData.fresnel.x + materialData.f90 * areaGGXData.fresnel.y;
	vec3 fresnelCore = specular;
#endif

	specular *= areaRectangleLight(N, coreView, P, areaGGXData.Minv, p0, p1, p2, p3);
	float diffusevalue = areaRectangleLight(N, V, P, mat3(1.0), p0, p1, p2, p3);

#ifdef EVisuPBR
	specular += diffusevalue * evisuMaterialData.specularEnergyConservationConstant;
	diffusevalue *= evisuMaterialData.diffuseEnergyConservationConstant;
	diffuse = evisuMaterialData.albedoEvisu * diffusevalue;
#else
	diffusevalue *= SpecGlossEnergyConservationTerm(fresnelCore);
	diffuse = materialData.albedo * diffusevalue;
#endif

#ifdef SO_EVISUPBR_SHEEN
	vec3 sheen = vec3(areaRectangleLight(N, V, P, areaGGXData.MinvSheen, p0, p1, p2, p3));
#ifdef SO_EVISUPBR_ver21x
	float sheenBlending = 1.0 - evisuMaterialData.sheenEnergyConservationConstant;
	diffuse *= sheenBlending;
	specular *= sheenBlending;
	specular += materialData.sheen * sheen * materialData.sheenColor;
#else
	float sheenBlending = 1.0 - vPow5(1.0 - materialData.sheen);
	diffuse *= PI * sheen * sheenBlending + (1.0 - sheenBlending);
#endif
#endif

#ifdef SO_EVISUPBR_SUBSURFACE
	vec3 transColor = vec3(0.0);
	transColor = materialData.albedo * (1.0 - materialData.metallic) * materialData.transparency;
#ifdef SO_EVISUPBR_TRANSLUCENCY
	vec3 scatteringColor = SssLUTSampling(materialData.curvature);
	diffuse = vMix(diffuse, scatteringColor * diffuse, materialData.translucency);
#endif
	diffuse += transColor * diffusevalue;
#elif defined(SO_EVISUPBR_TRANSLUCENCY)
	diffuse *= (1.0 - materialData.translucency);
	translucency = materialData.translucency * evisuMaterialData.albedoEvisu * areaRectangleLight(-N, V, P, mat3(1.0), p0, p1, p2, p3) * evisuMaterialData.diffuseEnergyConservationConstant;
#endif

#ifdef Flakes
	vec3 fresnelMetal = materialData.metallicFlakesF0 * areaGGXData.fresnelMetalFlakes.x;
	vec3 flakesVal = fresnelMetal;
	flakesVal *= areaRectangleLight(N, V, P, areaGGXData.MinvMetalFlakes, p0, p1, p2, p3);
	float energyFlakes = SpecGlossEnergyConservationTerm(fresnelMetal);
	specular *= energyFlakes;
	diffuse *= energyFlakes;
	specular += flakesVal;

	fresnelMetal = materialData.metalF0 * areaGGXData.fresnelMetal.x;
	flakesVal = fresnelMetal;
	flakesVal *= areaRectangleLight(N, V, P, areaGGXData.MinvMetal, p0, p1, p2, p3);
	energyFlakes = SpecGlossEnergyConservationTerm(fresnelMetal);
	specular *= energyFlakes;
	diffuse *= energyFlakes;
	specular += flakesVal;

	// Pearl flakes		
#if INTERNAL_FLAKES_PERFORMANCE_MODE < 1
	fresnelMetal = materialData.pearlFlakesF0 * areaGGXData.fresnelPearlFlakes.x;
	flakesVal = fresnelMetal;
	flakesVal *= areaRectangleLight(N, V, P, areaGGXData.MinvPearlFlakes, p0, p1, p2, p3);
	energyFlakes = SpecGlossEnergyConservationTerm(fresnelMetal);
	specular *= energyFlakes;
	diffuse *= energyFlakes;
	specular += flakesVal;
#endif
#endif

#ifdef SO_EVISUPBR_FLAKES
	vec3 flakesVal = vec3(0.0);
	if (evisuFlakesData.smooth_weight > 0.0) {
		flakesVal += evisuFlakesData.smooth_weight * areaRectangleLight(N, V, P, areaGGXData.MinvMetal, p0, p1, p2, p3);
	}
	if (evisuFlakesData.stochastic_weight > 0.0) {
		vec3 normalStoFlakes = vNormalize(V + evisuFlakesData.stochasticHemisphereFlakesOrientation);
		flakesVal += evisuFlakesData.stochastic_weight * areaRectangleLight(normalStoFlakes, V, P, areaGGXData.MinvMetalSto, p0, p1, p2, p3);
	}
	if (evisuFlakesData.closeup_weight > 0.0) {
		vec3 normalCloseUpFlakes = vNormalize(V + evisuFlakesData.voronoi_cell_orientation);
		flakesVal += evisuFlakesData.closeup_weight * areaRectangleLight(normalCloseUpFlakes, V, P, areaGGXData.MinvMetalClose, p0, p1, p2, p3);
	}
	diffuse *= evisuFlakesData.base_weight;
	specular *= evisuFlakesData.base_weight;
	specular += flakesVal * materialData.flakeColor;
#endif


#if defined(Coating) || defined(SO_EVISUPBR_CLEARCOAT)
	float clearCoat = 1.0;
#ifdef SO_EVISUPBR_CLEARCOAT
	clearCoat = materialData.clearcoat;
	vec3 fresnelCoat = COATINGF0 * areaGGXData.fresnelCC.x + COATINGF90 * areaGGXData.fresnelCC.y;
	float fresnelEnergy = 1.0 - materialData.clearcoat * vMaxComp(SchlickFresnel(COATINGF0, COATINGF90, vSaturate(vDot(materialData.clearcoatNormal, V))));
#else
	vec3 fresnelCoat = materialData.coatingF0 * areaGGXData.fresnelCC.x + COATINGF90 * areaGGXData.fresnelCC.y;
	float fresnelEnergy = SpecGlossEnergyConservationTerm(fresnelCoat);
#endif
	vec3 coat = fresnelCoat;
	coat *= areaRectangleLight(N, V, P, areaGGXData.MinvCC, p0, p1, p2, p3);
	diffuse *= fresnelEnergy;
	specular *= fresnelEnergy;
	specular += clearCoat * coat;
#endif

}

//Rect Light --

void areaDiskLightModel(out vec3 specular, out vec3 diffuse, out vec3 translucency, in vec3 N, in vec3 V, in vec3 P, in vec3 p0, in vec3 p1, in vec3 p2, in BRDFParameters materialData, in LightingData iData) {
#ifdef AnisotropicDistribution
	vec3 bitangent = vNormalize(vSin(2.0 * PI * materialData.anisotropyAngle) * iData.T + vCos(2.0 * PI * materialData.anisotropyAngle) * iData.B);
	vec3 anisotropicTangent = vCross(bitangent, -V);
	vec3 anisotropicNormal = vCross(anisotropicTangent, bitangent);
	vec3 bentNormal = vNormalize(vMix(N, anisotropicNormal, 0.22*materialData.anisotropy));
	vec3 coreView = vReflect(vReflect(V, bentNormal), N);
#else
	vec3 coreView = V;
#endif

#ifdef EVisuPBR
	specular = evisuMaterialData.sr0Color * areaGGXData.fresnel.x + evisuMaterialData.sr90Color * areaGGXData.fresnel.y;
#else
	specular = materialData.f0 * areaGGXData.fresnel.x + materialData.f90 * areaGGXData.fresnel.y;
	vec3 fresnelCore = specular;
#endif

	specular *= areaDiskLight(N, coreView, P, areaGGXData.Minv, p0, p1, p2);
	float diffusevalue = areaDiskLight(N, V, P, mat3(1.0), p0, p1, p2);

#ifdef EVisuPBR
	specular += diffusevalue * evisuMaterialData.specularEnergyConservationConstant;
	diffusevalue *= evisuMaterialData.diffuseEnergyConservationConstant;
	diffuse = evisuMaterialData.albedoEvisu * diffusevalue;
#else
	diffusevalue *= SpecGlossEnergyConservationTerm(fresnelCore);
	diffuse = materialData.albedo * diffusevalue;
#endif

#ifdef SO_EVISUPBR_SHEEN
	vec3 sheen = vec3(areaDiskLight(N, V, P, areaGGXData.MinvSheen, p0, p1, p2));
#ifdef SO_EVISUPBR_ver21x
	float sheenBlending = 1.0 - evisuMaterialData.sheenEnergyConservationConstant;
	diffuse *= sheenBlending;
	specular *= sheenBlending;
	specular += materialData.sheen * sheen * materialData.sheenColor;
#else
	float sheenBlending = 1.0 - vPow5(1.0 - materialData.sheen);
	diffuse *= PI * sheen * sheenBlending + (1.0 - sheenBlending);
#endif
#endif

#ifdef SO_EVISUPBR_SUBSURFACE
	vec3 transColor = vec3(0.0);
	transColor = materialData.albedo * (1.0 - materialData.metallic) * materialData.transparency;
#ifdef SO_EVISUPBR_TRANSLUCENCY
	vec3 scatteringColor = SssLUTSampling(materialData.curvature);
	diffuse = vMix(diffuse, scatteringColor * diffuse, materialData.translucency);
#endif
	diffuse += transColor * diffusevalue;
#elif defined(SO_EVISUPBR_TRANSLUCENCY)
	diffuse *= (1.0 - materialData.translucency);
	translucency = materialData.translucency * evisuMaterialData.albedoEvisu * areaDiskLight(-N, V, P, mat3(1.0), p0, p1, p2) * evisuMaterialData.diffuseEnergyConservationConstant;
#endif

#ifdef Flakes
	vec3 fresnelMetal = materialData.metallicFlakesF0 * areaGGXData.fresnelMetalFlakes.x;
	vec3 flakesVal = fresnelMetal;
	flakesVal *= areaDiskLight(N, V, P, areaGGXData.MinvMetalFlakes, p0, p1, p2);
	float energyFlakes = SpecGlossEnergyConservationTerm(fresnelMetal);
	specular *= energyFlakes;
	diffuse *= energyFlakes;
	specular += flakesVal;

	fresnelMetal = materialData.metalF0 * areaGGXData.fresnelMetal.x;
	flakesVal = fresnelMetal;
	flakesVal *= areaDiskLight(N, V, P, areaGGXData.MinvMetal, p0, p1, p2);
	energyFlakes = SpecGlossEnergyConservationTerm(fresnelMetal);
	specular *= energyFlakes;
	diffuse *= energyFlakes;
	specular += flakesVal;

	// Pearl flakes		
#if INTERNAL_FLAKES_PERFORMANCE_MODE < 1
	fresnelMetal = materialData.pearlFlakesF0 * areaGGXData.fresnelPearlFlakes.x;
	flakesVal = fresnelMetal;
	flakesVal *= areaDiskLight(N, V, P, areaGGXData.MinvPearlFlakes, p0, p1, p2);
	energyFlakes = SpecGlossEnergyConservationTerm(fresnelMetal);
	specular *= energyFlakes;
	diffuse *= energyFlakes;
	specular += flakesVal;
#endif
#endif

#ifdef SO_EVISUPBR_FLAKES
	vec3 flakesVal = vec3(0.0);
	if (evisuFlakesData.smooth_weight > 0.0) {
		flakesVal += evisuFlakesData.smooth_weight * areaDiskLight(N, V, P, areaGGXData.MinvMetal, p0, p1, p2);
	}
	if (evisuFlakesData.stochastic_weight > 0.0) {
		vec3 normalStoFlakes = vNormalize( V + evisuFlakesData.stochasticHemisphereFlakesOrientation );
		flakesVal += evisuFlakesData.stochastic_weight * areaDiskLight(normalStoFlakes,V,P,areaGGXData.MinvMetalSto, p0,p1,p2);
	}
	if (evisuFlakesData.closeup_weight > 0.0) {
		vec3 normalCloseUpFlakes = vNormalize(V + evisuFlakesData.voronoi_cell_orientation);
		flakesVal += evisuFlakesData.closeup_weight * areaDiskLight(normalCloseUpFlakes, V, P, areaGGXData.MinvMetalClose, p0, p1, p2);
	}
	diffuse *= evisuFlakesData.base_weight;
	specular *= evisuFlakesData.base_weight;
	specular += flakesVal * materialData.flakeColor;
#endif

#if defined(Coating) || defined(SO_EVISUPBR_CLEARCOAT)
	float clearCoat = 1.0;
#ifdef SO_EVISUPBR_CLEARCOAT
	clearCoat = materialData.clearcoat;
	vec3 fresnelCoat = COATINGF0 * areaGGXData.fresnelCC.x + COATINGF90 * areaGGXData.fresnelCC.y;
	float fresnelEnergy = 1.0 - materialData.clearcoat * vMaxComp(SchlickFresnel(COATINGF0, COATINGF90, vSaturate(vDot(materialData.clearcoatNormal, V))));
#else
	vec3 fresnelCoat = materialData.coatingF0 * areaGGXData.fresnelCC.x + COATINGF90 * areaGGXData.fresnelCC.y;
	float fresnelEnergy = SpecGlossEnergyConservationTerm(fresnelCoat);
#endif
	vec3 coat = fresnelCoat;
	coat *= areaDiskLight(N, V, P, areaGGXData.MinvCC, p0, p1, p2);
	diffuse *= fresnelEnergy;
	specular *= fresnelEnergy;
	specular += clearCoat * coat;
#endif
}

vec3 vOrthoProjectionOnLine(in vec3 P, in vec3 A, in vec3 B) {
	vec3 v = vNormalize(B - A);
	vec3 AP = P - A;
	return A + vDot(AP, v) * v;
}

float cropDiskLight(in vec3 lP, in vec3 p0, in vec3 p1, in float radius, in vec3 P, in vec3 N) {
	vec3 H1 = vOrthoProjectionOnLine(P, lP, p0);
	vec3 H2 = vOrthoProjectionOnLine(P, lP, p1);
	vec3 toOnDiskPlane = H1 + H2;
	vec3 inDisk = lP + vMin(radius, vLength(toOnDiskPlane)) * vNormalize(toOnDiskPlane);
	return vDot(inDisk - P, N) <= 1e-6 ? 0.0 : 1.0;
}

float cropRectangleLight(in vec3 lP, in vec3 p0, in vec3 p1, in vec3 p2, in vec3 p3, in vec3 P, in vec3 N) {
	float A = dot(vNormalize(lP - P), N);
	float B = dot(vNormalize(p0 - P), N);
	float C = dot(vNormalize(p1 - P), N);
	float D = dot(vNormalize(p2 - P), N);
	float E = dot(vNormalize(p3 - P), N);
	return max(A, max(B, max(C, max(D, E)))) <= 1e-6 ? 0.0 : 1.0;
}

#endif


void ComputeAreaLighting(inout LightingResult res, inout LightingData iData, in BRDFParameters iParameters)
{
#ifdef PDSFX_ENABLE_CUSTOM_BRDF_LAYER
	iData.lightType = LIGHT_TYPE_AREA;
#endif
	for (int i = 0; i < vMin(NumberOfAreaLights, _V_MAX_LIGHTS); ++i)
	{
#ifdef LightChannels
		if ((cb_cbVolMaterial.u_LightChannels & cb_LightChannels.AreaChannels[i]) == 0)
			continue;
#endif
		AreaLightData areaData;
		areaData.lightSpecAtt = 1.0;
		vec3 lightPoweredColor = _sbCbAreaLights.Color[i] * _sbCbAreaLights.PowerPerAreaUnit[i];
		vec3 lightPos = _sbCbAreaLights.Position[i];
		vec3 L = lightPos - iData.P;
		float lightDistSquare = vDot(L, L);
		areaData.lightDist = vSqrt(lightDistSquare);
		iData.L = L / areaData.lightDist;

		vec3 r = vReflect(iData.V, iData.N);

#ifdef NewAreaLights
		vec3 FinalDiffuse = vec3(0.0);
		vec3 FinalSpecular = vec3(0.0);
		vec3 FinalTranslucency = vec3(0.0);

#ifdef EVisuPBR
		computeBRDFParamforEvisu(iParameters);
		computeEvisuEnergyConservation(iData.N, iData.V, iParameters);
#ifdef SO_EVISUPBR_FLAKES
		computeFlakesParam(iParameters, iData);
#endif
#endif
		computeAreaGGXData(iParameters, iData);
#endif

		// Sphere light
		if (_sbCbAreaLights.Type[i] == SphereAreaType)
		{
			areaData.lightRadius = _sbCbAreaLights.Size[i].x;
			float sqrLightRadius = areaData.lightRadius * areaData.lightRadius;

#if defined(NewAreaLights) && (NewAreaLights == NEW_SPHERE_LIGHT || NewAreaLights == NEW_ALL_LIGHT)
			float sphereEnergyCorrection = vSqrt(2.0);
			float radius = areaData.lightRadius;
			vec3 diffuse = vec3(0.0);
			vec3 specular = vec3(0.0);
			vec3 translucency = vec3(0.0);
			vec3 p0, p1, p2;
			getSphereLightPoints(lightPos, iData.P, radius, p0, p1, p2);
			areaDiskLightModel(specular, diffuse, translucency, iData.N, iData.V, iData.P, p0, p1, p2, iParameters, iData);
			float attenuation = sphereEnergyCorrection * cropDiskLight(lightPos, p0, p1, radius, iData.P, iData.N);
			FinalDiffuse += attenuation * lightPoweredColor * diffuse;
			FinalSpecular += attenuation * lightPoweredColor * specular;
			FinalTranslucency += translucency * lightPoweredColor * sphereEnergyCorrection * cropDiskLight(lightPos, p0, p1, radius, iData.P, -iData.N);
#else			
			iData.irradiance = (sqrLightRadius / (vMax(sqrLightRadius, lightDistSquare))) * vClamp(vDot(iData.N, iData.L), 0.0, 1.0)
				* PI * lightPoweredColor;

			vec3 centerToRay = vDot(L, r) * r - L;
			areaData.closestPoint = L + centerToRay * vClamp(areaData.lightRadius / vLength(centerToRay), 0.0, 1.0);
#endif // NewAreaLights
		}

		// Tube light
		if (_sbCbAreaLights.Type[i] == TubeAreaType)
		{
			float radius = _sbCbAreaLights.Size[i].x;
			float halfLength = _sbCbAreaLights.Size[i].y * 0.5;
			vec3 tubeDir = _sbCbAreaLights.OtherDirection[i];
			vec3 P0 = lightPos - halfLength * tubeDir;
			vec3 P1 = lightPos + halfLength * tubeDir;

			vec3 spherePosition = closestPointOnSegment(P0, P1, iData.P);
			vec3 sphereUnormL = spherePosition - iData.P;
			vec3 sphereL = vNormalize(sphereUnormL);
			float sqrSphereDistance = vDot(sphereUnormL, sphereUnormL);

			float sqrLightRadius = radius*radius;
			iData.irradiance = vClamp(vDot(sphereL, iData.N), 0.0, 1.0) * (sqrLightRadius / sqrSphereDistance) * PI * lightPoweredColor;
			iData.L = sphereL;

			vec3 forward = vNormalize(closestPointOnLine(P0, P1, iData.P) - iData.P);
			vec3 up = vCross(tubeDir, forward);

			vec3 p0 = lightPos - tubeDir * halfLength + radius * up;
			vec3 p1 = lightPos - tubeDir * halfLength - radius * up;
			vec3 p2 = lightPos + tubeDir * halfLength - radius * up;
			vec3 p3 = lightPos + tubeDir * halfLength + radius * up;

			float solidAngle = rectangleSolidAngle(iData.P, p0, p1, p2, p3);

			// With this code disabled, it seems that we have better results (no higher diffuse in the tube middle and extremities)
			/*iData.irradiance += ( vClamp(vDot(vNormalize(p0-iData.P),iData.N), 0.0, 1.0) + vClamp(vDot(vNormalize(p1-iData.P),iData.N), 0.0, 1.0)
								+ vClamp(vDot(vNormalize(p2-iData.P),iData.N), 0.0, 1.0) + vClamp(vDot(vNormalize(p3-iData.P),iData.N), 0.0, 1.0)
								+ vClamp(vDot(vNormalize(lightPos-iData.P),iData.N), 0.0, 1.0) )
								* solidAngle * 0.2 * lightPoweredColor;*/

								// Replace the disabled code effect for enegy consistency
			iData.irradiance += solidAngle * lightPoweredColor;

			areaData.lightRadius = radius;

			vec3 L0 = P0 - iData.P;
			vec3 L1 = P1 - iData.P;
			vec3 Ld = L1 - L0;
			float RoL0 = dot(r, L0);
			float RoLd = dot(r, Ld);
			float L0oLd = dot(L0, Ld);
			float distLd = vLength(Ld);
			float t = (RoL0 * RoLd - L0oLd) / (distLd * distLd - RoLd * RoLd);

			areaData.closestPoint = L0 + Ld * vClamp(t, 0.0, 1.0); // Specular reference point on tube neutral axis
			vec3 posToLight = areaData.closestPoint - iData.P;
			areaData.lightDist = vSqrt(vDot(posToLight, posToLight)); // Distance from position to point on tube neutral axis

			vec3 centerToRay = vDot(areaData.closestPoint, r) * r - areaData.closestPoint;
			areaData.closestPoint = areaData.closestPoint + centerToRay * vClamp(radius / vLength(centerToRay), 0.0, 1.0);
		}

		// Planar light direction check
		vec3 lightNormal = _sbCbAreaLights.MainDirection[i];
		if (_sbCbAreaLights.Type[i] > TubeAreaType && vDot(-iData.L, lightNormal) < 0.0)
			continue;

		// Disk light
		if (_sbCbAreaLights.Type[i] == DiskAreaType)
		{
			float radius = _sbCbAreaLights.Size[i].x;

#if defined(NewAreaLights) && (NewAreaLights == NEW_DISK_LIGHT || NewAreaLights == NEW_ALL_LIGHT)
			vec3 diffuse = vec3(0.0);
			vec3 specular = vec3(0.0);
			vec3 translucency = vec3(0.0);

			vec3 right = getGeomT(lightNormal);
			vec3 up = getGeomBApprox(lightNormal, right);

			vec3 p0 = lightPos + right * radius - up * radius;
			vec3 p1 = lightPos - right * radius - up * radius;
			vec3 p2 = lightPos - right * radius + up * radius;

			areaDiskLightModel(specular, diffuse, translucency, iData.N, iData.V, iData.P, p0, p1, p2, iParameters, iData);
			float attenuation = cropDiskLight(lightPos, p0, p1, radius, iData.P, iData.N);
			FinalDiffuse += attenuation * lightPoweredColor * diffuse;
			FinalSpecular += attenuation * lightPoweredColor * specular;
			FinalTranslucency += translucency * lightPoweredColor * cropDiskLight(lightPos, p0, p1, radius, iData.P, -iData.N);
#else
			float sqrDiskRadius = radius*radius;
			iData.irradiance = vClamp(vDot(lightNormal, -iData.L), 0.0, 1.0) * vClamp(vDot(iData.N, iData.L), 0.0, 1.0) / (lightDistSquare / sqrDiskRadius + 1.0)
				* PI * lightPoweredColor;

			areaData.lightRadius = radius;

			float DNoDP = vDot(lightNormal, lightPos);
			float DNoR = vDot(lightNormal, r);
			float k = (DNoDP - vDot(iData.P, lightNormal)) / DNoR;
			vec3 interPlaneReflect = iData.P + k * r;
			vec3 lightToInter = interPlaneReflect - lightPos;

			areaData.closestPoint = L + lightToInter * vClamp(areaData.lightRadius / vLength(lightToInter), 0.0, 1.0);
#endif
		}

		// Rectangle light
		if (_sbCbAreaLights.Type[i] == RectangleAreaType)
		{
			float halfWidth = _sbCbAreaLights.Size[i].x * 0.5;
			float halfHeight = _sbCbAreaLights.Size[i].y * 0.5;
			vec3 rectangleUp = _sbCbAreaLights.OtherDirection[i];
			vec3 rectangleLeft = vCross(lightNormal, rectangleUp);

			vec3 p0 = lightPos + rectangleLeft * -halfWidth + rectangleUp *  halfHeight;
			vec3 p1 = lightPos + rectangleLeft * -halfWidth + rectangleUp * -halfHeight;
			vec3 p2 = lightPos + rectangleLeft *  halfWidth + rectangleUp * -halfHeight;
			vec3 p3 = lightPos + rectangleLeft *  halfWidth + rectangleUp *  halfHeight;


#if defined(NewAreaLights) && (NewAreaLights == NEW_RECT_LIGHT || NewAreaLights == NEW_ALL_LIGHT)
			vec3 diffuse = vec3(0.0);
			vec3 specular = vec3(0.0);
			vec3 translucency = vec3(0.0);

			areaRectangleLightModel(specular, diffuse, translucency, iData.N, iData.V, iData.P, p0, p1, p2, p3, iParameters, iData);
			float attenuation = cropRectangleLight(lightPos, p0, p1, p2, p3, iData.P, iData.N);
			FinalDiffuse += attenuation * lightPoweredColor * diffuse;
			FinalSpecular += attenuation * lightPoweredColor * specular;
			FinalTranslucency += translucency * lightPoweredColor *  cropRectangleLight(lightPos, p0, p1, p2, p3, iData.P, -iData.N);
#else
			float solidAngle = vMax(rectangleSolidAngle(iData.P, p0, p1, p2, p3), 0.0);

			// With this code disabled, it seems that we have better results (no higher diffuse in the rectangle middle and extremities)
			/*iData.irradiance = ( vClamp(vDot(vNormalize(p0-iData.P),iData.N), 0.0, 1.0) + vClamp(vDot(vNormalize(p1-iData.P),iData.N), 0.0, 1.0)
								+ vClamp(vDot(vNormalize(p2-iData.P),iData.N), 0.0, 1.0) + vClamp(vDot(vNormalize(p3-iData.P),iData.N), 0.0, 1.0)
								+ vClamp(vDot(vNormalize(lightPos-iData.P),iData.N), 0.0, 1.0) )
								* solidAngle * 0.2 * lightPoweredColor;*/

								// Replace the disabled code effect for enegy consistency
			iData.irradiance = solidAngle * lightPoweredColor;

			iData.L = vNormalize(vDot(L, lightNormal)*lightNormal);

			areaData.lightRadius = vSqrt(halfWidth*halfHeight); // Width/height geometric average

			float DNoDP = vDot(lightNormal, lightPos);
			float DNoR = vDot(lightNormal, r);
			float k = (DNoDP - vDot(iData.P, lightNormal)) / DNoR;
			vec3 interPlaneReflect = iData.P + k * r;
			vec3 lightToInter = interPlaneReflect - lightPos;
			vec2 lightToInterInPlane = vec2(vDot(lightToInter, rectangleLeft), vDot(lightToInter, rectangleUp));
			vec2 closestPointInPlane = vec2(vClamp(lightToInterInPlane.x, -halfWidth, halfWidth), vClamp(lightToInterInPlane.y, -halfHeight, halfHeight));

			vec2 neutralAxis = vec2(vClamp(halfWidth - halfHeight, 0.0, halfWidth), vClamp(halfHeight - halfWidth, 0.0, halfHeight));
			vec2 neutralAxisProj = vec2(vClamp(closestPointInPlane.x, -neutralAxis.x, neutralAxis.x), vClamp(closestPointInPlane.y, -neutralAxis.y, neutralAxis.y));

			areaData.closestPoint = L + neutralAxisProj.x * rectangleLeft + neutralAxisProj.y * rectangleUp; // Specular reference point on rectangle neutral axis
			vec3 posToLight = areaData.closestPoint - iData.P;
			areaData.lightDist = vSqrt(vDot(posToLight, posToLight)); // Distance from position to point on rectangle neutral axis

			areaData.closestPoint = L + closestPointInPlane.x * rectangleLeft + closestPointInPlane.y * rectangleUp;

			// Hack to reduce BRDF light lobs
			areaData.lightSpecAtt *= 1.0 - vDot(closestPointInPlane, closestPointInPlane) / (halfWidth*halfWidth + halfHeight*halfHeight);
#endif
		}

		//shadow map not yet implemented for area lights
		iData.visibility = vMax(1.0, iData.minVisibility);
		iData.lightID = i;

#ifdef NewAreaLights
		if ((_sbCbAreaLights.Type[i] == SphereAreaType) && (NewAreaLights == NEW_SPHERE_LIGHT || NewAreaLights == NEW_ALL_LIGHT))
		{
			res.radiance += (FinalDiffuse + FinalSpecular) * iData.visibility;
			res.radiance += FinalTranslucency;
		}
		else if ((_sbCbAreaLights.Type[i] == DiskAreaType) && (NewAreaLights == NEW_DISK_LIGHT || NewAreaLights == NEW_ALL_LIGHT))
		{
			res.radiance += (FinalDiffuse + FinalSpecular) * iData.visibility;
			res.radiance += FinalTranslucency;
		}
		else if ((_sbCbAreaLights.Type[i] == RectangleAreaType) && (NewAreaLights == NEW_RECT_LIGHT || NewAreaLights == NEW_ALL_LIGHT)) {
			res.radiance += (FinalDiffuse + FinalSpecular) * iData.visibility;
			res.radiance += FinalTranslucency;
		}
		else
			ComputeAreaLightEquation(iData, areaData, iParameters, res);
#else
		ComputeAreaLightEquation(iData, areaData, iParameters, res);
#endif

	}
}
#endif

#endif
